package info.motteke.logger;

import static java.util.logging.Level.*;
import info.motteke.lang.Lang;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.LogRecord;

/**
 * {@linkplain java.util.logging.Logger}をラップするクラスです。
 * 使用方法はjava.util.loggingパッケージに準拠しています。
 *
 * @author YWak
 *
 */
public class Logger {
    /**
     * java標準のロガー
     */
    private final java.util.logging.Logger logger;

    private static final Map<String, Logger> loggerPool;

    static {
        loggerPool = Collections.synchronizedMap(new HashMap<String, Logger>());
    }

    /**
     * コンストラクタです。<br>
     * ラップする対象を引数に取ります。
     *
     * @param logger
     *            java標準のロガー
     */
    private Logger(java.util.logging.Logger logger) {
        this.logger = logger;
    }

    /**
     * クラス名を指定してロガーを取得します。
     *
     * @param clazz
     *            ロガーを利用するクラス
     * @return ロガー
     */
    public static Logger getLogger(Class<?> clazz) {
        return getLogger(clazz.getName(), null);
    }

    /**
     * クラス名とリソースバンドルを指定してロガーを取得します。
     *
     * @param clazz
     *            ロガーを利用するクラス
     * @param resourceBundleName
     *            ロガーのメッセージのローカライズに使用されるリソースバンドルの名前
     * @return ロガー
     */
    public static Logger getLogger(Class<?> clazz, String resourceBundleName) {
        return getLogger(clazz.getName(), resourceBundleName);
    }

    /**
     * {@linkplain java.util.logging.Logger java標準のロガー}のインスタンスを作成します。
     *
     * @param name
     *            ロガーの名前
     * @param resourceBundleName
     *            ResourceBundle名
     * @return java標準のロガー
     */
    public static synchronized Logger getLogger(String name, String resourceBundleName) {
        Logger logger = loggerPool.get(name);
        if (logger == null) {
            logger = new Logger(java.util.logging.Logger.getLogger(name, resourceBundleName));
            loggerPool.put(name, logger);
        } else {
            String rb = logger.getResourceBundleName();
            if (Lang.equal(rb, resourceBundleName)) {
                String message = String.format("%sは既に違うリソースバンドル(%s)で登録されています。", name, rb);
                throw new IllegalArgumentException(message);
            }
        }
        return logger;
    }

    public String getResourceBundleName() {
        return logger.getResourceBundleName();
    }

    /**
     * {@linkplain java.util.logging.Level#FINEST FINEST}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     */
    public void finest(String msg) {
        log(FINEST, msg);
    }

    /**
     * {@linkplain java.util.logging.Level#FINEST FINEST}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     */
    public void finest(String msg, Throwable thrown) {
        log(FINEST, msg, thrown);
    }

    /**
     * {@linkplain java.util.logging.Level#FINEST FINEST}レベルのメッセージを出力します。<br>
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     * @param params
     *            メッセージに埋め込むパラメータ
     */
    public void finest(String msg, Throwable thrown, Object... params) {
        log(FINEST, msg, thrown, params);
    }

    /**
     * このロガーで、{@linkplain java.util.logging.Level#FINEST FINEST}レベルの出力が有効であるかを判断します。
     *
     * @return FINESTレベルの出力が有効ならtrue、さもなくばfalse
     */
    public boolean isFinestEnabled() {
        return logger.isLoggable(FINEST);
    }

    /**
     * {@linkplain java.util.logging.Level#FINER FINER}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     */
    public void finer(String msg) {
        log(FINER, msg);
    }

    /**
     * {@linkplain java.util.logging.Level#FINER FINER}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     */
    public void finer(String msg, Throwable thrown) {
        log(FINER, msg, thrown);
    }

    /**
     * {@linkplain java.util.logging.Level#FINER FINER}レベルのメッセージを出力します。<br>
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     * @param params
     *            メッセージに埋め込むパラメータ
     */
    public void finer(String msg, Throwable thrown, Object... params) {
        log(FINER, msg, thrown, params);
    }

    /**
     * このロガーで、{@linkplain java.util.logging.Level#FINER FINER}レベルの出力が有効であるかを判断します。
     *
     * @return FINERレベルの出力が有効ならtrue、さもなくばfalse
     */
    public boolean isFinerEnabled() {
        return logger.isLoggable(FINER);
    }

    /**
     * {@linkplain java.util.logging.Level#FINE FINE}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     */
    public void fine(String msg) {
        log(FINE, msg);
    }

    /**
     * {@linkplain java.util.logging.Level#FINE FINE}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     */
    public void fine(String msg, Throwable thrown) {
        log(FINE, msg, thrown);
    }

    /**
     * {@linkplain java.util.logging.Level#FINE FINE}レベルのメッセージを出力します。<br>
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     * @param params
     *            メッセージに埋め込むパラメータ
     */
    public void fine(String msg, Throwable thrown, Object... params) {
        log(FINE, msg, thrown, params);
    }

    /**
     * このロガーで、{@linkplain java.util.logging.Level#FINE FINE}レベルの出力が有効であるかを判断します。
     *
     * @return FINEレベルの出力が有効ならtrue、さもなくばfalse
     */
    public boolean isFineEnabled() {
        return logger.isLoggable(FINE);
    }

    /**
     * {@linkplain java.util.logging.Level#CONFIG CONFIG}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     */
    public void config(String msg) {
        log(CONFIG, msg);
    }

    /**
     * {@linkplain java.util.logging.Level#CONFIG CONFIG}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     */
    public void config(String msg, Throwable thrown) {
        log(CONFIG, msg, thrown);
    }

    /**
     * {@linkplain java.util.logging.Level#CONFIG CONFIG}レベルのメッセージを出力します。<br>
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     * @param params
     *            メッセージに埋め込むパラメータ
     */
    public void config(String msg, Throwable thrown, Object... params) {
        log(CONFIG, msg, thrown, params);
    }

    /**
     * このロガーで、{@linkplain java.util.logging.Level#CONFIG CONFIG}レベルの出力が有効であるかを判断します。
     *
     * @return CONFIGレベルの出力が有効ならtrue、さもなくばfalse
     */
    public boolean isConfigEnabled() {
        return logger.isLoggable(CONFIG);
    }

    /**
     * {@linkplain java.util.logging.Level#INFO INFO}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     */
    public void info(String msg) {
        log(INFO, msg);
    }

    /**
     * {@linkplain java.util.logging.Level#INFO INFO}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     */
    public void info(String msg, Throwable thrown) {
        log(INFO, msg, thrown);
    }

    /**
     * {@linkplain java.util.logging.Level#INFO INFO}レベルのメッセージを出力します。<br>
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     * @param params
     *            メッセージに埋め込むパラメータ
     */
    public void info(String msg, Throwable thrown, Object... params) {
        log(INFO, msg, thrown, params);
    }

    /**
     * このロガーで、{@linkplain java.util.logging.Level#INFO INFO}レベルの出力が有効であるかを判断します。
     *
     * @return INFOレベルの出力が有効ならtrue、さもなくばfalse
     */
    public boolean isInfoEnabled() {
        return logger.isLoggable(INFO);
    }

    /**
     * {@linkplain java.util.logging.Level#WARNING WARNING}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     */
    public void warning(String msg) {
        log(WARNING, msg);
    }

    /**
     * {@linkplain java.util.logging.Level#WARNING WARNING}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     */
    public void warning(String msg, Throwable thrown) {
        log(WARNING, msg, thrown);
    }

    /**
     * {@linkplain java.util.logging.Level#WARNING WARNING}レベルのメッセージを出力します。<br>
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     * @param params
     *            メッセージに埋め込むパラメータ
     */
    public void warning(String msg, Throwable thrown, Object... params) {
        log(WARNING, msg, thrown, params);
    }

    /**
     * このロガーで、{@linkplain java.util.logging.Level#WARNING WARNING}レベルの出力が有効であるかを判断します。
     *
     * @return WARNINGレベルの出力が有効ならtrue、さもなくばfalse
     */
    public boolean isWarningEnabled() {
        return logger.isLoggable(WARNING);
    }

    /**
     * {@linkplain java.util.logging.Level#SEVERE SEVERE}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     */
    public void severe(String msg) {
        log(SEVERE, msg);
    }

    /**
     * {@linkplain java.util.logging.Level#SEVERE SEVERE}レベルのメッセージを出力します。
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     */
    public void severe(String msg, Throwable thrown) {
        log(SEVERE, msg, thrown);
    }

    /**
     * {@linkplain java.util.logging.Level#SEVERE SEVERE}レベルのメッセージを出力します。<br>
     *
     * @param msg
     *            メッセージ
     * @param thrown
     *            関連するThrowable
     * @param params
     *            メッセージに埋め込むパラメータ
     */
    public void severe(String msg, Throwable thrown, Object... params) {
        log(SEVERE, msg, thrown, params);
    }

    /**
     * このロガーで、{@linkplain java.util.logging.Level#SEVERE SEVERE}レベルの出力が有効であるかを判断します。
     *
     * @return SEVEREレベルの出力が有効ならtrue、さもなくばfalse
     */
    public boolean isSevereEnabled() {
        return logger.isLoggable(SEVERE);
    }

    /**
     * メソッドエントリのログを取得します。<br>
     * ログレベルFINERで出力されます。
     *
     * @param sourceClass
     *            ロギングの要求を発行したクラス名
     * @param sourceMethod
     *            エントリしているメソッド名
     * @param params
     *            エントリしているメソッドのパラメータの配列
     *
     * @see java.util.logging.Logger#entering(String, String, Object[])
     */
    public void entering(String sourceClass, String sourceMethod, Object... params) {
        logger.entering(sourceClass, sourceMethod, params);
    }

    /**
     * メソッド復帰のログを取得します。<br>
     * ログレベルFINERで出力されます。
     *
     * @param sourceClass
     *            ロギングの要求を発行したクラス名
     * @param sourceMethod
     *            メソッドの名前
     * @param result
     *            返されたオブジェクト
     *
     * @see java.util.logging.Logger#exiting(String, String, Object)
     */
    public void exiting(String sourceClass, String sourceMethod, Object result) {
        logger.exiting(sourceClass, sourceMethod, result);
    }

    /**
     * メソッド復帰のログを取得します。<br>
     * ログレベルFINERで出力されます。
     *
     * @param sourceClass
     *            ロギングの要求を発行したクラス名
     * @param sourceMethod
     *            メソッドの名前
     *
     * @see java.util.logging.Logger#exiting(String, String)
     */
    public void exiting(String sourceClass, String sourceMethod) {
        logger.exiting(sourceClass, sourceMethod);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }

        if (!(obj instanceof Logger)) {
            return false;
        }
        return logger.equals(((Logger) obj).logger);

    }

    @Override
    public int hashCode() {
        return logger.hashCode();
    }

    @Override
    public String toString() {
        return String.format("Logger(%s)", logger.getName());
    }

    private void log(Level level, String msg) {
        log(level, msg, null, null);
    }

    private void log(Level level, String msg, Throwable thrown) {
        log(level, msg, thrown, null);
    }

    /**
     * ログを登録します。
     *
     * @param level
     *            作成するログイベントのログレベル
     * @param msg
     *            ログイベントのメッセージか、メッセージカタログのキー
     * @param thrown
     *            ログイベントに関連するThrowable
     * @param params
     *            ログメッセージに埋め込まれるパラメータ
     * @return ログイベント
     */
    private void log(Level level, String msg, Throwable thrown, Object[] params) {
        LogEvent record = new LogEvent(level, msg);
        record.setLoggerName(logger.getName());
        record.setParameters(params);
        record.setResourceBundleName(logger.getResourceBundleName());
        record.setResourceBundle(logger.getResourceBundle());
        record.setThrown(thrown);
        log(record);
    }

    /**
     * ログイベントを登録します。
     *
     * @param record
     */
    private void log(LogRecord record) {
        logger.log(record);
    }
}
